/**
 * 
 */
package edu.cmu.mism.dgjava.algorithm.impl;

import org.eclipse.core.runtime.IProgressMonitor;

import edu.cmu.mism.dgjava.data.models.option.BaseType;
import edu.cmu.mism.dgjava.data.models.option.BlackScholes;
import edu.cmu.mism.dgjava.data.models.option.Equity;
import edu.cmu.mism.dgjava.data.models.option.OptionCalculationResult;
import edu.cmu.mism.dgjava.data.models.option.impl.OptionModelFactoryImpl;

/**
 * @author Ankit
 * 
 */
public class BlackScholesAlg extends OptionsAlg {

	private double actualOptionValue;
	private double optionValue;
	private double d1;
	private double d2;

	/**
	 * @param underlyingModel
	 * @param pricingModel
	 */
	public BlackScholesAlg(Object underlyingModel, Object pricingModel) {
		super(underlyingModel, pricingModel);
		// TODO Auto-generated constructor stub
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see edu.cmu.mism.dgjava.algorithm.impl.OptionsAlg#calculateDelta()
	 */
	@Override
	protected void calculateDelta() {
		 
		if(type.getValue() == BaseType.CALL_VALUE){
			delta = getNormalDistribution(d1);
		} else {
			delta = getNormalDistribution(d1) - 1;
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see edu.cmu.mism.dgjava.algorithm.impl.OptionsAlg#calculateGamma()
	 */
	@Override
	protected void calculateGamma() {
		
		gamma = calculateN_dashOfNumber(d1)/(stockPrice*volatility*Math.sqrt(timeToExpiration));

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see edu.cmu.mism.dgjava.algorithm.impl.OptionsAlg#calculateRho()
	 */
	@Override
	protected void calculateRho() {
		
		initializeOptionsVariable();
		riskFreeRate = riskFreeRate + 0.01;
		calculateOption();
		rho = (optionValue - actualOptionValue)/(0.01*100);

//		if(type.getValue() == BaseType.CALL_VALUE){
//			rho = strikePrice*timeToExpiration*Math.pow(Math.E, -(riskFreeRate*timeToExpiration))*getNormalDistribution(d2);
//		} else {
//			rho = -(strikePrice*timeToExpiration*Math.pow(Math.E, -(riskFreeRate*timeToExpiration))*getNormalDistribution(-d2));
//		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see edu.cmu.mism.dgjava.algorithm.impl.OptionsAlg#calculateTheta()
	 */
	@Override
	protected void calculateTheta() {
		
		if(type.getValue() == BaseType.CALL_VALUE){
			theta = -(stockPrice * calculateN_dashOfNumber(d1)*volatility)/(2*Math.sqrt(timeToExpiration)) - riskFreeRate*strikePrice*Math.pow(Math.E, -(riskFreeRate*timeToExpiration)*getNormalDistribution(d2));
			theta /= 365.0;
		} else {
			theta = -(stockPrice * calculateN_dashOfNumber(d1)*volatility)/(2*Math.sqrt(timeToExpiration)) + riskFreeRate*strikePrice*Math.pow(Math.E, -(riskFreeRate*timeToExpiration)*getNormalDistribution(-d2));
			theta /= 365.0;
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see edu.cmu.mism.dgjava.algorithm.impl.OptionsAlg#calculateVega()
	 */
	@Override
	protected void calculateVega() {
		
		initializeOptionsVariable();
		volatility = volatility + 0.01;
		calculateOption();
		vega = (optionValue - actualOptionValue)/(0.01*100);

		
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * edu.cmu.mism.dgjava.algorithm.IOptionAlgorithm#calculate(org.eclipse.
	 * core.runtime.IProgressMonitor)
	 */
	@Override
	public OptionCalculationResult calculate(IProgressMonitor monitor) {

		OptionCalculationResult result = OptionModelFactoryImpl.eINSTANCE
				.createOptionCalculationResult();

		initializeOptionsVariable();
		calculateOption();
		actualOptionValue = optionValue;
		result.setPrice(optionValue);
		calculateGreekLetters();
		result.setDelta(delta);
		result.setGamma(gamma);
		result.setTheta(theta);
		result.setVega(vega);
		result.setRho(rho);
		return result;
	}

	/**
	 * 
	 */
	private void initializeOptionsVariable() {

		stockPrice = ((Equity) _underlyingModel).getStockPrice();
		riskFreeRate = ((Equity) _underlyingModel).getRiskFreeRate() / 100.0;
		volatility = ((Equity) _underlyingModel).getVolatility() / 100.0;

		strikePrice = ((BlackScholes) _pricingModel).getExercisePrice();
		timeToExpiration = ((BlackScholes) _pricingModel).getTimeToExercise();

		type = ((BlackScholes) _pricingModel).getType();

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see edu.cmu.mism.dgjava.algorithm.impl.OptionsAlg#calculateOption()
	 */
	@Override
	protected void calculateOption() {
		
		d1 = ((Math.log(stockPrice/strikePrice) + ((riskFreeRate + (volatility * volatility)/2)* timeToExpiration)))/(volatility * Math.sqrt(timeToExpiration));
		d2 = d1 - volatility*Math.sqrt(timeToExpiration);

		if (type.getValue() == BaseType.CALL_VALUE) {
			
			optionValue = stockPrice * getNormalDistribution(d1) - strikePrice * getNormalDistribution(d2) * Math.pow(Math.E, -riskFreeRate * timeToExpiration);

		} else {

			optionValue = strikePrice * getNormalDistribution(-d2) * Math.pow(Math.E, -riskFreeRate * timeToExpiration) - stockPrice * getNormalDistribution(-d1);
		}

	}

	/**
	 * @param z
	 * @return
	 * 
	 *         Source: http://www1.fpl.fs.fed.us/CDF_Normal.java
	 */
	private double getNormalDistribution(double value) {

		double absoluteValue = Math.abs(value);
		
		final double a1 = 0.319381530;
		final double a2 = -0.356563782;
		final double a3 = 1.781477937;
		final double a4 = -1.821255978;
		final double a5 = 1.330274429;
		final double lambda = 0.2316419;
		
		double k = 1 / (1 + lambda*absoluteValue);
		
		double n_dashOfX = calculateN_dashOfNumber(value);
		
		// distribution = 1 - N'(x) * (a1*k + a2*k*k + a3*k*k*k + a4*k*k*k*k + a5*k**k*k*k*k);
		double distribution = 1 - n_dashOfX * (a1*k + a2 * k * k + a3 * k * k * k + a4 * k * k * k * k + a5 * k * k * k * k * k);
		
		if(value >= 0){
			return distribution;
		} else {
			return 1 - distribution;
		}
		
	}

	/**
	 * @param value
	 * @return
	 */
	private double calculateN_dashOfNumber(double value) {
		
		
		return (1 / Math.sqrt(2 * Math.PI)) * Math.pow(Math.E, -(value * value) / 2);
	}

}
